﻿using System;
using System.Windows;
using System.Windows.Input;

namespace Monti.MVVM.Commands
{
   /// <summary>
   /// Defines the attached properties to create a CommandBehaviorBinding
   /// </summary>
   public class CommandBehavior : Freezable
   {
      #region Properties

      CommandBehaviorBinding m_Behavior;
      DependencyObject m_Owner;

      /// <summary>
      /// Stores the Command Behavior Binding
      /// </summary>
      internal CommandBehaviorBinding Behavior
      {
         get { return this.m_Behavior ?? (this.m_Behavior = new CommandBehaviorBinding ()); }
      }

      /// <summary>
      /// Gets or sets the Owner of the binding
      /// </summary>
      public DependencyObject Owner
      {
         get { return this.m_Owner; }
         set
         {
            this.m_Owner = value;
            ResetEventBinding (this.m_Owner, this.Behavior, this.Event);
         }
      }

      #endregion

      #region Behavior

      /// <summary>
      /// Behavior Attached Dependency Property
      /// </summary>
      private static readonly DependencyProperty s_BehaviorProperty =
          DependencyProperty.RegisterAttached ("Behavior", typeof (CommandBehaviorBinding), typeof (CommandBehavior),
              new FrameworkPropertyMetadata ((CommandBehaviorBinding)null));

      /// <summary>
      /// Gets the Behavior property. 
      /// </summary>
      private static CommandBehaviorBinding GetBehavior (DependencyObject d)
      {
         return (CommandBehaviorBinding)d.GetValue (s_BehaviorProperty);
      }

      /// <summary>
      /// Sets the Behavior property.  
      /// </summary>
      private static void SetBehavior (DependencyObject d, CommandBehaviorBinding value)
      {
         d.SetValue (s_BehaviorProperty, value);
      }

      #endregion

      #region Command

      /// <summary>
      /// Command Attached Dependency Property
      /// </summary>
      public static readonly DependencyProperty CommandProperty =
          DependencyProperty.RegisterAttached ("Command", typeof (ICommand), typeof (CommandBehavior),
              new FrameworkPropertyMetadata (null, OnCommandChanged));

      /// <summary>
      /// Gets or sets the Command property.  
      /// </summary>
      public ICommand Command
      {
         get { return (ICommand)GetValue (CommandProperty); }
         set { SetValue (CommandProperty, value); }
      }

      /// <summary>
      /// Gets the Command property.  
      /// </summary>
      public static ICommand GetCommand (DependencyObject d)
      {
         return (ICommand)d.GetValue (CommandProperty);
      }

      /// <summary>
      /// Sets the Command property. 
      /// </summary>
      public static void SetCommand (DependencyObject d, ICommand value)
      {
         d.SetValue (CommandProperty, value);
      }

      /// <summary>
      /// Handles changes to the Command property.
      /// </summary>
      private static void OnCommandChanged (DependencyObject d, DependencyPropertyChangedEventArgs e)
      {
         var commandBehavior = d as CommandBehavior;
         if (commandBehavior != null)
            commandBehavior.OnCommandChanged (e);
         else
         {
            CommandBehaviorBinding binding = FetchOrCreateBinding (d);
            binding.Command = (ICommand)e.NewValue;
         }
      }

      /// <summary>
      /// Provides derived classes an opportunity to handle changes to the Command property.
      /// </summary>
      protected virtual void OnCommandChanged (DependencyPropertyChangedEventArgs e)
      {
         this.Behavior.Command = Command;
      }

      #endregion

      #region Action

      /// <summary>
      /// Action Attached Dependency Property
      /// </summary>
      public static readonly DependencyProperty ActionProperty =
          DependencyProperty.RegisterAttached ("Action", typeof (Action<object>), typeof (CommandBehavior),
              new FrameworkPropertyMetadata (null,
                  OnActionChanged));

      /// <summary>
      /// Gets or sets the Action property. 
      /// </summary>
      public Action<object> Action
      {
         get { return (Action<object>)GetValue (ActionProperty); }
         set { SetValue (ActionProperty, value); }
      }

      /// <summary>
      /// Gets the Action property.  
      /// </summary>
      public static Action<object> GetAction (DependencyObject d)
      {
         return (Action<object>)d.GetValue (ActionProperty);
      }

      /// <summary>
      /// Sets the Action property. 
      /// </summary>
      public static void SetAction (DependencyObject d, Action<object> value)
      {
         d.SetValue (ActionProperty, value);
      }

      /// <summary>
      /// Handles changes to the Action property.
      /// </summary>
      private static void OnActionChanged (DependencyObject d, DependencyPropertyChangedEventArgs e)
      {
         var commandBehavior = d as CommandBehavior;
         if (commandBehavior != null)
            commandBehavior.OnActionChanged (e);
         else
         {
            CommandBehaviorBinding binding = FetchOrCreateBinding (d);
            binding.Action = (Action<object>)e.NewValue;
         }
      }

      /// <summary>
      /// Provides derived classes an opportunity to handle changes to the Action property.
      /// </summary>
      protected virtual void OnActionChanged (DependencyPropertyChangedEventArgs e)
      {
         this.Behavior.Action = Action;
      }

      #endregion

      #region CommandParameter

      /// <summary>
      /// CommandParameter Attached Dependency Property
      /// </summary>
      public static readonly DependencyProperty CommandParameterProperty =
          DependencyProperty.RegisterAttached ("CommandParameter", typeof (object), typeof (CommandBehavior),
              new FrameworkPropertyMetadata (null, OnCommandParameterChanged));

      /// <summary>
      /// Gets or sets the CommandParameter property.  
      /// </summary>
      public object CommandParameter
      {
         get { return GetValue (CommandParameterProperty); }
         set { SetValue (CommandParameterProperty, value); }
      }

      /// <summary>
      /// Gets the CommandParameter property.  
      /// </summary>
      public static object GetCommandParameter (DependencyObject d)
      {
         return d.GetValue (CommandParameterProperty);
      }

      /// <summary>
      /// Sets the CommandParameter property. 
      /// </summary>
      public static void SetCommandParameter (DependencyObject d, object value)
      {
         d.SetValue (CommandParameterProperty, value);
      }

      /// <summary>
      /// Handles changes to the CommandParameter property.
      /// </summary>
      private static void OnCommandParameterChanged (DependencyObject d, DependencyPropertyChangedEventArgs e)
      {
         var commandBehavior = d as CommandBehavior;
         if (commandBehavior != null)
            commandBehavior.OnCommandParameterChanged (e);
         else
         {
            CommandBehaviorBinding binding = FetchOrCreateBinding (d);
            binding.CommandParameter = e.NewValue;
         }
      }

      /// <summary>
      /// Provides derived classes an opportunity to handle changes to the CommandParameter property.
      /// </summary>
      protected virtual void OnCommandParameterChanged (DependencyPropertyChangedEventArgs e)
      {
         this.Behavior.CommandParameter = CommandParameter;
      }

      #endregion

      #region Event

      /// <summary>
      /// Event Attached Dependency Property
      /// </summary>
      public static readonly DependencyProperty EventProperty =
          DependencyProperty.RegisterAttached ("Event", typeof (string), typeof (CommandBehavior),
              new FrameworkPropertyMetadata (String.Empty, OnEventChanged));

      /// <summary>
      /// Gets or sets the Event property.  
      /// </summary>
      public string Event
      {
         get { return (string)GetValue (EventProperty); }
         set { SetValue (EventProperty, value); }
      }

      /// <summary>
      /// Gets the Event property.  This dependency property 
      /// indicates ....
      /// </summary>
      public static string GetEvent (DependencyObject d)
      {
         return (string)d.GetValue (EventProperty);
      }

      /// <summary>
      /// Sets the Event property.  This dependency property 
      /// indicates ....
      /// </summary>
      public static void SetEvent (DependencyObject d, string value)
      {
         d.SetValue (EventProperty, value);
      }

      /// <summary>
      /// Handles changes to the Event property.
      /// </summary>
      private static void OnEventChanged (DependencyObject d, DependencyPropertyChangedEventArgs e)
      {
         var commandBehavior = d as CommandBehavior;
         if (commandBehavior != null)
            commandBehavior.OnEventChanged (e);
         else
         {
            CommandBehaviorBinding binding = FetchOrCreateBinding (d);
            ResetEventBinding (d, binding, e.NewValue.ToString ());
         }
      }

      /// <summary>
      /// Provides derived classes an opportunity to handle changes to the Event property.
      /// </summary>
      protected virtual void OnEventChanged (DependencyPropertyChangedEventArgs e)
      {
         ResetEventBinding (this.m_Owner, this.Behavior, this.Event);
      }

      #endregion

      #region Helpers

      /// <summary>
      /// Resets the even binding.
      /// </summary>
      private static void ResetEventBinding (DependencyObject owner, CommandBehaviorBinding commandBehavior, string eventName)
      {
         if (owner != null) //only do this when the Owner is set
         {
            //check if the Event is set. If yes we need to rebind the Command to the new event and unregister the old one
            if (commandBehavior.Event != null && commandBehavior.Owner != null)
               commandBehavior.Dispose ();

            //bind the new event to the command
            commandBehavior.BindEvent (owner, eventName);
         }
      }

      //tries to get a CommandBehaviorBinding from the element. Creates a new instance if there is not one attached
      private static CommandBehaviorBinding FetchOrCreateBinding (DependencyObject d)
      {
         CommandBehaviorBinding binding = GetBehavior (d);
         if (binding == null)
         {
            binding = new CommandBehaviorBinding ();
            SetBehavior (d, binding);
         }
         return binding;
      }

      #endregion

      #region Overrides

      /// <summary>
      /// When implemented in a derived class, creates a new instance of
      /// the <see cref="T:System.Windows.Freezable"/> derived class. 
      /// </summary>
      /// <returns>The new instance.</returns>
      protected override Freezable CreateInstanceCore ()
      {
         throw new NotImplementedException ();
      }

      #endregion
   }
}
